package com.gdhz.xiaohengqin.listener;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gdhz.xiaohengqin.device.constants.WieGandConstant;
import com.gdhz.xiaohengqin.device.model.WieGandRequest;
import com.gdhz.xiaohengqin.device.model.WieGandResponse;
import com.gdhz.xiaohengqin.device.model.entity.DoorInfo;
import com.gdhz.xiaohengqin.device.service.AccessGateService;
import com.gdhz.xiaohengqin.events.PassthroughDataEvent;
import com.gdhz.xiaohengqin.reservation.constants.TeleOneConstant;
import com.gdhz.xiaohengqin.reservation.model.ReservationRecordResponse;
import com.gdhz.xiaohengqin.reservation.service.ReservationRecordService;
import com.gdhz.xiaohengqin.service.SystemConfigService;
import jakarta.xml.bind.DatatypeConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.LocalTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * @author Swallows
 * @date 2025-06-03 11:36
 */
@Slf4j
@Component
public class MessageEventListener {

    private final ReservationRecordService reservationRecordService;
    private final AccessGateService accessGateService;
    private final SystemConfigService systemConfigService;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    public MessageEventListener(ReservationRecordService reservationRecordService,
                                AccessGateService accessGateService,
                                SystemConfigService systemConfigService) {
        this.reservationRecordService = reservationRecordService;
        this.accessGateService = accessGateService;
        this.systemConfigService = systemConfigService;
    }

    @EventListener
    public Mono<Void> handleQrCodeData(PassthroughDataEvent event) {
        // 1. 先判断当前门是否可用 可用则执行步骤 2 不可用则终止流程
        // 2. 在判断是当前门如果是进门还是出门 如果是出门则执行步骤4
        // 3. 如果是进门 则在判断在允许入园时间范围 不在范围 则终止流程
        // 4. 如果在范围则解析二维码 Hex 数据 调用verifyByQrCode 进行验证
        // 5. 如果是有效二维码且是出门的话 则直接调用开门方法 如果是有效且是进门的话 调用verifyById 确认到场
        return getDoorInfo(event.data().getDeviceSn(),  event.data().getDoorNo())
                .flatMap(doorInfo -> processDoorEvent(event, doorInfo))
                .onErrorResume(e -> {
                    log.error(" 处理二维码事件失败: deviceSn={}", event.data().getDeviceSn(),  e);
                    return Mono.empty();
                });
    }

// --- 拆解后的子方法 ---

    /**
     * 分步处理门禁事件
     */
    private Mono<Void> processDoorEvent(PassthroughDataEvent event, DoorInfo doorInfo) {
        // 1. 检查门状态
        if (doorInfo.getState()  != 0) {
            log.warn(" 此门未启用，终止流程. deviceSn={}, doorNo={}, state={}",
                    event.data().getDeviceSn(),  event.data().getDoorNo(),  doorInfo.getState());
            return Mono.empty();
        }

        // 2. 判断进出方向
        if (WieGandConstant.DOOR_OUT.equals(doorInfo.getInout()))  {
            log.info("SN: {}, 门号: {}, 执行出门扫码操作", event.data().getDeviceSn(),  event.data().getDoorNo());
            // 出门直接处理
            return processExitDoor(event, doorInfo);
        } else {
            // 进门需要检查时间范围
            log.info("SN: {}, 门号: {}, 执行进门扫码操作", event.data().getDeviceSn(),  event.data().getDoorNo());
            return processEntryDoor(event, doorInfo);
        }
    }

    /**
     * 处理出门逻辑
     */
    private Mono<Void> processExitDoor(PassthroughDataEvent event, DoorInfo doorInfo) {
        return parseAndVerifyQrCode(event)
                .flatMap(qrCodeResponse -> {
                    if (isValidQrCode(qrCodeResponse)) {
                        return triggerDoorOpening(event, qrCodeResponse.getData().getId(),  doorInfo);
                    }
                    return Mono.empty();
                });
    }

    /**
     * 处理进门逻辑
     */
    private Mono<Void> processEntryDoor(PassthroughDataEvent event, DoorInfo doorInfo) {
        // 在调用处添加日志
        log.info(" 开始检查入园时间...");
        return checkEntryTimeRange()
                .doOnSubscribe(s -> log.info("checkEntryTimeRange  已订阅"))
                .doOnNext(inRange -> log.info("checkEntryTimeRange  返回值: {}", inRange))
                .flatMap(inRange -> {
                    log.info(" 实际检查结果: {}", inRange);
                    if (!inRange) {
                        log.warn(" 当前时间不在允许入园时间范围内");
                        return Mono.empty();
                    }

                    return parseAndVerifyQrCode(event)
                            .doOnNext(resp -> log.info("QR  码原始数据: {}", resp))
                            .flatMap(qrCodeResponse -> {
                                log.info(" 开始校验 QR 码有效性");
                                if (!isValidQrCode(qrCodeResponse)) {
                                    log.warn("QR  码无效，终止流程");
                                    return Mono.empty();
                                }
                                log.info("QR  码有效，继续验证预约记录");
                                return reservationRecordService.verifyById(qrCodeResponse.getData().getId())
                                        .doOnNext(verificationResult -> log.info(" 预约记录验证结果: {}", verificationResult.toString()))
                                        .flatMap(verificationResult -> {
                                            if ("1".equals(verificationResult.getData().getVisitStatus()))  {
                                                log.info(" 状态合法，触发开门");
                                                return triggerDoorOpening(event, qrCodeResponse.getData().getId(),  doorInfo);
                                            } else {
                                                log.error(" 状态非法: visitStatus={}", verificationResult.getData().getVisitStatus());
                                                return Mono.empty();
                                            }
                                        });
                            });
                });
    }

    // 辅助方法
    private Mono<ReservationRecordResponse> parseAndVerifyQrCode(PassthroughDataEvent event) {
        String url = parseQrCodeData(event);
        return reservationRecordService.verifyByQrCode(url, TeleOneConstant.XIAO_HENG_QING_TOKEN_PROD);
    }

    private boolean isValidQrCode(ReservationRecordResponse response) {
        if (!TeleOneConstant.CODE_10000000.equals(response.getCode()))  {
            log.error(" 二维码核验失败: code={}, message={}", response.getCode(),  response.getMessage());
            return false;
        }
        return true;
    }

    /**
     * 解析二维码Hex数据
     */
    private String parseQrCodeData(PassthroughDataEvent event) {
        byte[] bytes = DatatypeConverter.parseHexBinary(event.data().getHexData());
        log.info("解析到的二维码数据: {}", new String(bytes, StandardCharsets.UTF_8));
        return new String(bytes, StandardCharsets.UTF_8);
    }


    /**
     * 触发远程开门
     */
    private Mono<Void> triggerDoorOpening(PassthroughDataEvent event, String recordId, DoorInfo doorInfo) {
        // 1. 检查门状态
        if (doorInfo.getState()  != 0) {
            log.warn(" 门禁止通行: deviceSn={}, doorNo={}, state={}",
                    event.data().getDeviceSn(),  event.data().getDoorNo(),  doorInfo.getState());
            return Mono.empty();
        }

        // 2. 构建请求并发送开门指令
        WieGandRequest request = buildDoorOpenRequest(event);

        return accessGateService.sendActuationCommand(request)
                .doOnSuccess(json -> {
                    // 空值检查
                    if (json.getResult()  == null && json.getError()  == null) {
                        log.error(" 设备返回数据异常: result 和 error 均为 null");
                        return;
                    }

                    // 处理成功
                    if (json.getResult()  != null && json.getResult().isSuccess()  && json.getResult().getCode()  == 1) {
                        log.info(" 开门成功: deviceSn={}, recordId={}", event.data().getDeviceSn(),  recordId);
                    }
                    // 处理失败
                    else {
                        String errorMsg = Optional.ofNullable(json.getError())
                                .map(WieGandResponse.Result::getMessage)
                                .orElseGet(() -> Optional.ofNullable(json.getResult())
                                        .map(WieGandResponse.Result::getMessage)
                                        .orElse("未知错误"));
                        log.error(" 开门失败: error={}", errorMsg);
                    }
                })
                .onErrorResume(e -> {
                    log.error(" 设备通信异常", e);
                    return Mono.empty();
                })
                .then();
    }


    /**
     * 检查当前时间是否在允许入园时间范围内
     */
    private Mono<Boolean> checkEntryTimeRange() {
        return Mono.zip(
                        systemConfigService.getConfig( "entry_start_time")
                                .filter(Objects::nonNull) // 过滤 null 值
                                .switchIfEmpty(Mono.error(new  IllegalArgumentException("入园开始时间为空"))),
                        systemConfigService.getConfig("entry_end_time")
                                .filter(Objects::nonNull) // 过滤 null 值
                                .switchIfEmpty(Mono.error(new  IllegalArgumentException("入园结束时间为空"))),
                        (startTimeStr, endTimeStr) -> {
                            LocalTime now = LocalTime.now();
                            LocalTime startTime = LocalTime.parse(startTimeStr);
                            LocalTime endTime = LocalTime.parse(endTimeStr);

                            boolean inRange = !now.isBefore(startTime)  && !now.isAfter(endTime);
                            log.info(" 入园时间检查 - 当前时间: {}, 允许范围: {} 至 {}, 结果: {}",
                                    now, startTime, endTime, inRange);

                            return inRange;
                        }
                )
                .doOnNext(result -> log.debug(" 最终入园时间检查结果: {}", result))
                .onErrorResume(e -> {
                    log.error(" 获取入园时间配置失败", e);
                    return Mono.just(false);
                })// 如果获取配置失败，默认不允许入园
                .doOnSuccess(result -> log.info("checkEntryTimeRange  完成，结果: {}", result));
    }

    /**
     * 构建开门请求
     */
    private WieGandRequest buildDoorOpenRequest(PassthroughDataEvent event) {
        WieGandRequest request = new WieGandRequest();
        WieGandRequest.Params param = new WieGandRequest.Params();
        request.setMethod("远程开门");
        param.setDeviceSn(event.data().getDeviceSn());
        param.setDoorNo(event.data().getDoorNo());
        request.setParams(List.of(param));
        return request;
    }

    /**
     * 查找门信息
     */
    private Mono<DoorInfo> getDoorInfo(Integer gateSn, Integer doorNo) {
        return accessGateService.findBySn(gateSn)
                .switchIfEmpty(Mono.error(new  IllegalArgumentException("未找到设备: " + gateSn)))
                .flatMap(accessGate -> {
                    try {
                        List<DoorInfo> doorInfos = objectMapper.convertValue(
                                accessGate.getDoorInfo(),
                                new TypeReference<>() {
                                }
                        );
                        return Mono.justOrEmpty(
                                doorInfos.stream()
                                        .filter(doorInfo -> doorNo.equals(doorInfo.getDoorNo()))
                                        .findFirst()
                        );
                    } catch (JsonProcessingException e) {
                        log.error(" 解析门信息失败", e);
                        return Mono.empty();
                    }
                });
    }

}
